#!/bin/sh
# burncd - Burn audio files to a blank CD.

# Copyright 2016-2020 orbea
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# This is required for the legacy ash in Slackware and can be removed
# once ash is no longer included in any supported Slackware releases.
# shellcheck disable=SC2004
# https://github.com/koalaman/shellcheck/wiki/SC2004

tab='	'
IFS=" $tab
"
\unset -f command printf read unalias : 2>/dev/null
\unalias -a 2>/dev/null
PATH="$(command -p getconf PATH):$PATH"
LC_ALL=C; export LC_ALL

set -euf

chkdir () {
  if [ ! -d "$1" ]; then
    die 1 "$PRGNAM: '$1': Not a directory." "$help"
  elif [ ! -x "$1" ]; then
    die 1 "$PRGNAM: Cannot open directory '$1': Permission denied." "$help"
  elif [ ! -r "$1" ]; then
    die 1 "$PRGNAM: Cannot read directory '$1': Permission denied." "$help"
  else
    return 0
  fi
  return 1
}

chkformat () {
  case "$1" in
    au|flac|mp3|wav ) return 0 ;;
    * ) return 1 ;;
  esac
}

chkformat2 () {
  if chkformat "$1"; then
    eval "tmpv=\"\$$2\""
    match "$1" "$tmpv" || eval "$2=\"\$${2} \$1\""
  else
    die 1 "$PRGNAM: Unrecognized audio format '$1'" "$help"
  fi
}

chkvar () {
  chkvar=1
  case "$2" in
    ''|CONFIG|PLAYLIST ) addvar "$1" "${2:-files}" ;;
    EXCLUDE|FORMAT ) chkformat2 "$1" "$2" ;;
    INPUT ) ! chkdir "$1" || addvar "${1%${1##*[!/]}}/" "$2" ;;
    * ) eval "$2=\"$1\""; VAR= ;;
  esac
}

die () {
  ret="$1"; shift
  case "$ret" in
    : ) printf %s\\n "$@" >&2; return 0 ;;
    0 ) printf %s\\n "$@" ;;
    * ) printf %s\\n "$@" >&2 ;;
  esac
  exit "$ret"
}

escape () {
  word="$1"; base=
  case "$word" in
    *\\*|*\$*|*\"*|*\`* )
      while [ "$word" != '' ]; do
        first="$(printf %s "${word%"${word#?}"}")"
        word="$(printf %s "${word#?}")"
        case "$first" in
          \\|\$|\"|\` ) first="\\$first" ;;
          * ) : ;;
        esac
        base="${base}$first"
      done
    ;;
    * )
      base="$word"
    ;;
  esac
}

handleargs () {
  case "$1" in
    -- ) shift; for i do addvar "$i" files; done; return 1 ;;
    - ) while IFS= read -r stdin; do addvar "$stdin" files; done; VAR= ;;
    -c|--config ) VAR=CONFIG ;;
    -C|--cdrecord ) VAR=CDRECORD ;;
    -d|--debug ) DEBUG=1; VAR= ;;
    -e|--exclude ) VAR=EXCLUDE ;;
    -f|--format ) VAR=FORMAT ;;
    -F|--flac ) VAR=FLAC ;;
    -h|--help ) die 0 "$usage" ;;
    -i|--input ) VAR=INPUT ;;
    -M|--mpg123 ) VAR=MPG123 ;;
    -n|--dry-run ) DRYRUN=1; VAR= ;;
    -N|--no-conf ) NOCONFIG=1; VAR= ;;
    -o|--output ) VAR=OUTPUT ;;
    -p|--playlist ) VAR=PLAYLIST ;;
    -S|--sox ) VAR=SOX ;;
    -v|--verbose ) VERBOSE=1; VAR= ;;
    -V|--version ) die 0 "$PRGNAM 0.0" ;;
    -z|--device ) VAR=DEVICE ;;
    --* ) die 1 "$PRGNAM: Unrecognized option '$1'" "$help" ;;
    -* ) shortargs "${1#-}" ;;
    * ) chkvar "$1" "$VAR" ;;
  esac
}

shortargs () {
  arg="$1"
  while [ -n "${arg}" ]; do
    flag="$(printf %s "${arg%"${arg#?}"}")"
    arg="$(printf %s "${arg#?}")"
    case "$flag" in
      c ) option=config ;;
      C ) option=cdrecord ;;
      d ) option=debug ;;
      e ) option=exclude ;;
      f ) option=format ;;
      F ) option=flac ;;
      h ) option=help ;;
      i ) option=input ;;
      M ) option=mpg123 ;;
      n ) option=dry-run ;;
      N ) option=no-conf ;;
      o ) option=output ;;
      p ) option=playlist ;;
      S ) option=sox ;;
      v ) option=verbose ;;
      V ) option=version ;;
      z ) option=device ;;
      * ) die 1 "$PRGNAM: Unrecognized option -- '$flag'" "$help" ;;
    esac
    handleargs "--$option"
    [ -z "${VAR}" ] || { [ -z "${arg}" ] || chkvar "$arg" "$VAR"; break; }
  done
}

addvar () { escape "$1"; eval "$2=\"\${$2} \\\"\$base\"\\\""; }
match () { case "$2" in *$1*) return 0 ;; *) return 1 ;; esac; }
next () { case "$1" in -*) return 0 ;; *) return 1 ;; esac; }

files=; tmpv=

CDRECORD=; CONFIG=; DEBUG=; DEVICE=; DRYRUN=; EXCLUDE=; FORMAT=; FLAC=; INPUT=
MPG123=; NOCONFIG=; OUTPUT=; PLAYLIST=; SOX=; VAR=; VERBOSE=

PRGNAM=burncd
CONF=$PRGNAM.conf

help="Try '$PRGNAM --help' for more information."

usage="$PRGNAM - Burn audio files to a blank CD from the command line, current
         directory, playlist or standard input.

  Usage: $PRGNAM [OPTIONS] [file ...]
    -c, --config,   Path of the configuration files.
    -C, --cdrecord, Path of the cdrecord binary.
    -d, --debug,    Debug output from external programs.
    -e, --exclude,  Exclude audio formats.
    -f, --format,   Choose the audio format.
    -F, --flac,     Path of the flac binary.
    -h, --help,     Show this help message.
    -i, --input,    Path of the default directories.
    -M, --mpg123,   Path of the mpg123 binary.
    -n, --dry-run,  Enable a test run without burning to a CD.
    -N, --no-conf,  Do not load default configuration files.
    -o, --output,   Path of the temporary directory.
    -p, --playlist, Read audio files from a playlist.
    -S, --sox,      Path of the sox binary.
    -v, --verbose,  Verbose logging.
    -V. --version,  Show the $PRGNAM version number.
    -z, --device,   Path of the CD drive.

  When 'file' is -, read standard input.

  Supported audio formats:
    au, flac, mp3, wav

  Environment variables:
    XDG_CONFIG_HOME : XDG base directory.
    XDG_CONFIG_DIRS : List of colon-separated directories.

  Configuration files:
    $PRGNAM looks for configuration files in the following order:

    1. \$XDG_CONFIG_HOME/$PRGNAM/$CONF
       (Default: \$HOME/.config/$PRGNAM/$CONF)
    2. \$HOME/.$CONF
    3. \$XDG_CONFIG_DIRS/$PRGNAM/$CONF
       (Default: /etc/xdg/$PRGNAM/$CONF)
    4. /etc/$CONF
    5. ./$CONF

  To use cdrecord as a normal user:
    # chown root:somegroup /usr/bin/cdrecord
    # chmod 4710 /usr/bin/cdrecord"

while [ $# -gt 0 ]; do
  chkvar=
  flag="$1"
  handleargs "$@" || break
  shift
  if [ -n "${VAR}" ] && [ -z "${chkvar}" ] && next "${1--}"; then
    next "${flag#-}" ||
      die 1 "$PRGNAM: option requires an argument -- '${flag#-}'" "$help"
    die 1 "$PRGNAM: option '$flag' requires an argument" "$help"
  fi
done

chkdep () {
  for deps do
    bin="${deps##*:}"
    DEP="$(exists "$bin")" || return 1
    eval "${deps%:*}=\"\$DEP\""
  done
  return 0
}

exists () {
  r=0; cwd="$(pwd)"
  while [ $# -gt 0 ]; do
    v=1; arg="$1"; shift
    case "$arg" in
      ''|*/ )
        :
      ;;
      /* )
        if [ -f "$arg" ] && [ -x "$arg" ]; then
          printf %s\\n "$arg"
          v=0
        fi
      ;;
      ./* )
        if [ -f "$arg" ] && [ -x "$arg" ]; then
          pre="$(cd -- "${arg%%/*}/" && pwd)"
          printf %s\\n "${pre%/}/$arg"
          v=0
        fi
      ;;
      */* )
        if [ -f "$arg" ] && [ -x "$arg" ]; then
          printf %s\\n "$(cd -- "${arg%%/*}/.." && pwd)/$arg"
          v=0
        fi
      ;;
      * )
        if [ -n "${PATH+x}" ]; then
          p=":${PATH:-$cwd}"
          while [ "$p" != "${p#*:}" ] && [ -n "${p#*:}" ]; do
            p="${p#*:}"; x="${p%%:*}"; z="${x:-$cwd}"; d="${z%/}/$arg"
            if [ -f "$d" ] && [ -x "$d" ]; then
              case "$d" in
                /* ) : ;;
                ./* ) pre="$(cd -- "${d%/*}/" && pwd)"; d="${pre%/}/$d" ;;
                * ) d="$(cd -- "${d%/*}/" && pwd)/$arg" ;;
              esac
              printf %s\\n "$d"
              v=0
              break
            fi
          done
        fi
      ;;
    esac
  [ $v = 0 ] || r=1
  done
  return $r
}

chkdep FILE:file LS:ls MKDIR:mkdir MV:mv READLINK:readlink RM:rm ||
  die 1 "ERROR: no $bin in ($PATH)"

skipping="${tab}Skipping"

# contains: '\r', '^M', ' ', '\t' and '\n'
SPACE="$IFS"

addconf () {
  [ -n "${1}" ] || return 0
  eval "CONF_$var=\"\${2:-$1}\""
  addvar "${tab}$var='$1'" set
}

chkconf () {
  case "$1" in
    CDRECORD|DEBUG|DEVICE|DRYRUN|EXCLUDE|FORMAT|FLAC|INPUT|MPG123|OUTPUT|SOX| \
    VERBOSE )
      return 0
    ;;
  esac
  return 1
}

chkfile () {
  if [ -h "$1" ]; then
    in="$(command -p "${READLINK:?}" -- "$1")"
  else
    in="$1"
  fi
  shift
  file="$(command -p "${FILE:?}" "$@" "$in")"
  type="$(printf %s "${file#"$in: "}")"
}

chktext () {
  if [ ! -r "$1" ]; then
    die : "WARNING: $2 file cannot be read." "$skipping '$1'."
    return 1
  fi
  chkfile "$1" -i --
  case "$type" in
    *empty*|*text* ) return 0 ;;
    * ) die : "WARNING: $2 is not a text file." "$skipping '$1'."; return 1 ;;
  esac
}

conf () {
  if chktext "$1" Config; then
    set=
    line=1
    while read -r var chr val cmt; do
      if ! comment "$var"; then
        if [ "$chr" = = ]; then
          if chkconf "$var"; then
            if ! comment "$val"; then
              eval "tmpv=\"\$$var\""
              if empty "$tmpv"; then
                add=
                if [ "$var" = EXCLUDE ] || [ "$var" = FORMAT ]; then
                  for i in $(printf %s "$val $cmt"); do
                    if comment "$i"; then
                      break
                    elif chkformat "$i"; then
                      match "$i" "$add" || add="${add} $i"
                    else
                      die : "WARNING: $1:$line:" \
                        "$skipping unknown audio format '$i'."
                    fi
                  done
                  addconf "${add# }"
                elif [ "$var" = INPUT ]; then
                  for i in $(printf %s "$val $cmt"); do
                    if comment "$i"; then
                      break
                    elif [ ! -d "$i" ]; then
                      die : "WARNING: $1:$line:" \
                        "${tab}'$i': Not a directory."
                    elif [ ! -x "$i" ]; then
                      die : "WARNING: $1:$line:" \
                        "${tab}Cannot open directory '$i': Permission denied."
                    elif [ ! -r "$i" ]; then
                      die : "WARNING: $1:$line:" \
                        "${tab}Cannot read directory '$i': Permission denied."
                    else
                      add="${add} \"${i%${i##*[!/]}}/\""
                    fi
                  done
                  addconf "${add# }"
                else
                  escape "$val"
                  addconf "$base" "$val"
                fi
                match "$var" "$confv" || confv="${confv} $var"
              fi
              [ "$var" = EXCLUDE ] || [ "$var" = FORMAT ] ||
                [ "$var" = INPUT ] || comment "$cmt" ||
                die : "WARNING: $1:$line:" "$skipping excess values '$cmt'."
            fi
          else
            die : "WARNING: $1:$line:" "$skipping unknown variable '$var'."
          fi
        elif comment "$chr"; then
          die : "WARNING: $1:$line:" "${tab}Missing = operand."
        else
          die : "WARNING: $1:$line:" "${tab}Unrecognized character '$chr'."
        fi
      fi
      line=$(($line+1))
    done < "$in"
    addvar "Loaded configuration file '$1'." confl
    [ -z "$set" ] || { eval "set -- $set"
      confl="${confl} $(printf ' "%s"' "$@")"; }
    return 0
  fi
  return 1
}

play () {
  if chktext "$1" Playlist; then
    while read -r title; do
      empty "$title" || addvar "$title" files
    done < "$in"
    addvar "Loaded playlist file '$1'." playl
    return 0
  fi
  return 1
}

xdgdirs () {
  p=":$1"
  while [ "$p" != "${p#*:}" ]; do
    p="${p#*:}"
    dir="${p%%:*}"
    { ! empty "$dir" && conf "$dir/$PRGNAM/$CONF"; } || continue
    return 0
  done
  return 1
}

chkesc () { case "$1" in '') base="$2" ;; *) escape "$2" ;; esac ; }
comment () { case "$1" in \#*|'') return 0 ;; *) return 1 ;; esac; }
empty () { case "${1:-}" in *[!$SPACE]*) return 1 ;; *) return 0 ;; esac; }
exclude () { case "$EXCLUDE" in *$1*) fmt= ;; *) fmt="$2"; suf="$3" ;; esac; }
printn () { eval "set -- $1"; printf %s\\n $#; }
quit () { err=$?; eval "set -- $out $tmp"; command -p "${RM:?}" -f -- "$@"; }

play=; playl=

eval "set -- $PLAYLIST"
for f do
  ! play "$f" || play="${play} $f"
done

conf=; confl=; confv=

eval "set -- $CONFIG"
for f do
  ! conf "$f" || conf="${conf} $f"
done

[ -n "${NOCONFIG}" ] || [ -n "${conf}" ] ||
  conf "${XDG_CONFIG_HOME:-${HOME:=}/.config}/$PRGNAM/$CONF" ||
  conf "$HOME/.$CONF" || xdgdirs "${XDG_CONFIG_DIRS:-/etc/xdg}" ||
  conf /etc/$CONF || conf ./$CONF || :

eval "set -- $confv"
for var do
  eval "$var=\"\$CONF_$var\""
done

if [ -z "${files}" ] && [ -z "${play}" ]; then
  input=
  eval "set -- $INPUT"
  for f do
    empty "$(command -p "${LS:?}" "$f")" || input="${input} \"$f\"*"
  done
fi

if [ -n "${files}" ] || [ -n "${play}" ]; then
  eval "set -- $files"
elif [ -n "${input}" ]; then
  set +f
  eval "set -- $input"
  set -f
elif [ -z "${INPUT}" ] && ! empty "$(command -p "${LS:?}")"; then
  set +f
  set -- ./*
  set -f
else
  die 1 'ERROR: Directory is empty.'
fi

case "$DRYRUN" in
  1 ) dry () { :; } ;;
  * ) dry () { "$@"; } ;;
esac

case "$VERBOSE" in
  1 ) log () { printf %s\\n "$@"; } ;;
  * ) log () { :; } ;;
esac

case "$DEBUG" in
  1 ) quiet () { "$@"; } ;;
  * ) quiet () { "$@" 2>/dev/null; } ;;
esac

addtrack () {
  track="${2%.*}"
  eval "set -- $1"
  for i do
    a="${i##*/}"
    [ "${a%.*}" != "$track" ] || return 1
  done
  return 0
}

DEVICE="${DEVICE:-/dev/sr0}"
OUTPUT="${OUTPUT:-/tmp/CD}"

CP='cp'

CDRECORD="${CDRECORD:-cdrecord}"
FLAC="${FLAC:-flac}"
MPG123="${MPG123:-mpg123}"
SOX="${SOX:-sox}"

if [ "$VERBOSE" = 1 ]; then
  [ -z "${confl}" ] || ( eval "set -- $confl"; log '' "$@" )
  [ -z "${playl}" ] || ( eval "set -- $playl"; log '' "$@" )
fi

log '' 'Configured options:' \
  "${tab}DEVICE = $DEVICE" \
  "${tab}OUTPUT = $OUTPUT" \
  "${tab}Preferred audio formats = ${FORMAT# }" \
  "${tab}Excluded audio formats = ${EXCLUDE# }" \
  "${tab}Playlist files = ${play# }" \
  "${tab}Configuration files = ${conf# }" ''

if [ -z "${FORMAT}" ]; then
  FORMAT='wav au flac mp3'
else
  for l in wav au flac mp3; do
    match "$l" "$FORMAT" || FORMAT="${FORMAT} $l"
  done
fi

if [ -z "${EXCLUDE}" ]; then
  final="$FORMAT"
else
  final=
  for l in $(printf %s "$FORMAT"); do
    match "$l" "$EXCLUDE" || final="${final} $l"
  done
fi

cmp=; skip=
au=; flac=; mp3=; wav=

m=0; n=0

for f do
  [ -d "$f" ] || [ -r "$f" ] ||
    { die : 'WARNING: File cannot be read.' "$skipping '$f'."; continue; }
  chkfile "$f" --
  case "$type" in
    *FLAC* ) exclude flac farr wav ;;
    *layer\ III* ) exclude mp3 marr wav ;;
    *Sun/NeXT* ) exclude au narr au ;;
    *WAVE* ) exclude wav warr wav ;;
    * ) fmt= ;;
  esac
  if [ -n "${fmt}" ]; then
    if addtrack "$cmp" "${in##*/}"; then
      name="${track%.*}.$suf"
      if [ $m -ge 99 ]; then
        chkesc "$files" "$f"
        skip="$skip \"${tab}$base\""
        continue
      elif [ -e "$OUTPUT/$name" ] || [ -e "$OUTPUT/tmp-$name" ]; then
        die : "WARNING: File found in $OUTPUT." "$skipping '$f'."
        continue
      fi
      m=$(($m+1))
    fi
    chkesc "$files" "$in"
    cmp="$cmp \"$base\""
    cmn="$type::${base}:$n:$fmt:$suf"
    case $fmt in
      farr ) flac="$flac \"${cmn}:\\\$FLAC:16 bit:44.1 kHz:stereo\"" ;;
      marr ) mp3="$mp3 \"${cmn}:\\\$MPG123: :44.1 kHz:Stereo\"" ;;
      narr ) au="$au \"${cmn}:\\\$CP:16-bit:44100 Hz:stereo\"" ;;
      warr ) wav="$wav \"${cmn}:\\\$CP:16 bit:44100 Hz:stereo\"" ;;
      * ) die 1 "ERROR: Unknown format name '$fmt'." ;;
    esac
    n=$(($n+1))
  elif [ -n "${files}" ]; then
    die : 'WARNING: Not a recognized audio file.' "$skipping '$f'."
  fi
done

[ -n "${cmp}" ] || die 1 'ERROR: No music files found.'

if [ -n "${skip}" ]; then
  eval "set -- $skip"
  die : 'WARNING: Audio CDs only allows 99 tracks.' "$skipping:" "$@" ''
fi

audio=

eval "set -- $final"
for f do
  eval "audio=\"\${audio} \${$f}\""
done

cmd=; new=; ord=; out=; req=; tmp=
farr=; marr=; narr=; warr=

eval "set -- $audio"
for f do
  song="${f#*::}"
  for i in cnl khz bit ext end lst pos; do
    eval "$i=\"\${song##*:}\""
    song="${song%:*}"
  done
  addtrack "$new" "${song##*/}" || continue
  var=
  type="${f%%::*}"
  addvar "$song" new
  head="${base##*/}"
  name="${head%.*}.${end:?}"
  ord="$ord \"$OUTPUT/$name:${pos:?}\""
  out="$out \"$OUTPUT/$name\""
  tmp="$tmp \"$OUTPUT/tmp-$name\""
  eval "${lst:?}=\"\${$lst} \\\"\$base\"\\\""
  match "${ext:?}" "$req" || req="${req} $ext"
  for s in "${bit:?}" "${khz:?}" "${cnl:?}"; do
    case "$type" in *$s*) : ;; *)
      case "$s" in
        "$bit" ) var="${var} -b 16" ;;
        "$khz" ) var="${var} -r 44.1k" ;;
        "$cnl" ) var="${var} -c 2" ;;
      esac ;;
    esac
  done
  [ -z "${var}" ] || cmd="$cmd \"$name:${var#"${var%%[! ]*}"}\""
done

for prgnam in \$CDRECORD $(printf %s "$req"); do
  dep="$(eval "printf %s \"$prgnam\"")"
  if chkdep "${prgnam#$}:$dep"; then
    log "$(eval "printf %s \"$prgnam\""): ok"
    continue
  elif empty "$dep"; then
    case "$prgnam" in
      \$CDRECORD ) dep=cdrecord ;;
      \$FLAC ) dep=flac ;;
      \$MPG123 ) dep=mpg123 ;;
      * ) die 1 "ERROR: Unknown variable name '$prgnam'." ;;
    esac
  fi
  die 1 "ERROR: no $dep in ($PATH)."
done

if SOX="$(exists "$SOX")"; then
  sec=
  eval "set -- $new"
  for t in $(quiet command -p "$SOX" --i -D -- "$@"); do
    sec="$(($sec+$(printf '%.0f' "$t")))"
  done
  dur="$(printf '%dh %dm %ds' $(($sec/3600)) $(($sec%3600/60)) $(($sec%60)))"
  log "$SOX: ok" "Total duration: $dur"
  [ "$sec" -le 4800 ] ||
    die 1 "ERROR: Total duration of $dur exceeds 80 minutes."
elif [ -n "${cmd}" ]; then
  die 1 'ERROR: sox is required for converting to 16 bit / 44.1 kHz / stereo.'
else
  die : 'WARNING: sox is required for checking the duration of the audio files.'
fi

while [ ! -e "$DEVICE" ]; do
  die : "WARNING: $DEVICE not found." 'Configure the device. (i.e. /dev/sr0)'
  read -r DEVICE
done

if [ -z "${DRYRUN}" ]; then
  while :; do
    blank="$(quiet command -p "$CDRECORD" dev="$DEVICE" -minfo)" ||
      die 1 "$CDRECORD: operation failed."
    case "$blank" in *Blank*) : ;; *)
      die : "WARNING: Blank CD not found in $DEVICE." \
        'Insert a blank CD. Continue? (y/n)'
      read -r answer
      case "$answer" in
        [yY]|[yY][eE][sS] ) continue ;;
        * ) die 0 "Blank CD not found in $DEVICE. Aborting ..." ;;
      esac ;;
    esac
    break
  done
fi

log "Using blank CD found in $DEVICE."

list=; fburn=; fmsg=; mburn=; mmsg=; nburn=; nmsg=; wburn=; wmsg=

for b in "$farr" "$marr" "$narr" "$warr"; do
  case "$b" in
    '' )
      :
    ;;
    "$farr" )
      fburn="quiet command -p \"\$FLAC\" --output-prefix=\"\$OUTPUT\"/ \
             --decode -- \"\$@\""
      fmsg='Decoding flac files ...'
      list="$list \"'\\\$farr' '\\\$fburn' '\\\$fmsg'\""
    ;;
    "$marr" )
      mburn="for a do fn=\"\${a##*/}\"; quiet command -p \"\$MPG123\" \
             --no-control -w \"\$OUTPUT/\${fn%.*}.wav\" \"\$a\"; done"
      mmsg='Converting mp3 files ...'
      list="$list \"'\\\$marr' '\\\$mburn' '\\\$mmsg'\""
    ;;
    "$narr" )
      nburn="quiet printf '%s\\n' \"\$@\"; for a do fn=\"\${a##*/}\"; \
             command -p \"\$CP\" -- \"\$a\" \"\$OUTPUT/\${fn%.*}.au\"; done"
      nmsg='Copying Sun/NeXT audio files ...'
      list="$list \"'\\\$narr' '\\\$nburn' '\\\$nmsg'\""
    ;;
    "$warr" )
      wburn="quiet printf '%s\\n' \"\$@\"; for a do fn=\"\${a##*/}\"; \
             command -p \"\$CP\" -- \"\$a\" \"\$OUTPUT/\${fn%.*}.wav\"; done"
      wmsg='Copying wav files ...'
      list="$list \"'\\\$warr' '\\\$wburn' '\\\$wmsg'\""
    ;;
  esac
done

: "$fburn" "$fmsg" "$mburn" "$mmsg" "$nburn" "$nmsg" "$wburn" "$wmsg" "$CP" \
  "$DEP" "$SPACE"

command -p "${MKDIR:?}" -p -- "$OUTPUT"

if [ "$(printn "$list")" -gt 1 ]; then
  out=; n=0
  arg=$(printn "$ord")
  while [ "$(printn "$out")" -lt "$arg" ]; do
    eval "set -- $ord"
    for i do
      if [ "${i##*:}" = $n ]; then
        addvar "${i%:*}" out
        break
      fi
    done
    n=$(($n+1))
  done
fi

trap 'quit; trap - EXIT; exit $err' EXIT INT

eval "set -- $list"
for i do
  eval "set -- $i"
  eval "burn=$2"
  eval "printf '%s\\n' \"$3\""
  eval "eval \"set -- $1\""
  eval "${burn:?}" || die 1 "ERROR: $PRGNAM operation failed."
done

eval "set -- $cmd"
for i do
  file="${i%:*}"
  opt="${i##*:}"
  log "Converting $file" "sox options: $opt"
  eval "set -- $opt"
  quiet command -p "$SOX" -- "$OUTPUT/$file" "$@" -- "$OUTPUT/tmp-$file"
  command -p "${RM:?}" -f -- "$OUTPUT/$file"
  command -p "${MV:?}" -- "$OUTPUT/tmp-$file" "$OUTPUT/$file"
done

eval "set -- $out"
log 'Burning audio files:' "$@"
dry quiet command -p "$CDRECORD" dev="$DEVICE" -dao -audio -pad -- "$@"
die 0 'CD burning finished successfully.'
